#include <map>
#include <string>
#include <stdexcept>
#include <iostream>

#include "HL2ServerInfo.h"
#include "ServerInfo.h"

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>

#include <compat.h>
#include <config.h>

using namespace std;

HL2ServerInfo::~HL2ServerInfo()
{
	this->closeConnection();
}

HL2ServerInfo::HL2ServerInfo(struct sockaddr_in *_addr)
{
	memcpy(&addr, &_addr, sizeof(struct sockaddr_in));
	sock = -1;
	challenge = 0;
}

HL2ServerInfo::HL2ServerInfo(const char *_addr, short port)
{
	char buf[BUFSIZ];
	struct hostent he;
	int herrno;
	struct hostent *result = NULL;

	sock = -1;
	challenge = 0;

	memset(&addr, 0, sizeof(struct sockaddr_in));
	this->addr.sin_family = AF_INET;
	this->addr.sin_port = htons(port);

	if(gethostbyname_r(_addr, &he, buf, sizeof(buf), &result, &herrno) == 0) {
		memcpy(&this->addr.sin_addr.s_addr, he.h_addr_list[0], he.h_length);
	} else {
		throw invalid_argument("Unabe to resolve hostname");
	}
}

uint32_t HL2ServerInfo::getChallenge(int timeout_sec = 3, int timeout_usec =0)
{
	char reply[128];
	char request[] = { 0xff, 0xff, 0xff, 0xff, 0x57 };
	if(this->challenge)
		return this->challenge;
	int sock =  this->openConnection();
	int r;
	struct timeval tv;
	fd_set rset;
	tv.tv_sec = timeout_sec;
	tv.tv_usec = timeout_usec;

	write(sock, request, sizeof(request));

	FD_ZERO(&rset);
	FD_SET(sock, &rset);
	if((r = select(sock+1, &rset, NULL, NULL, &tv))>0) {
		r = read(sock, reply, sizeof(reply));
		memcpy(&this->challenge, reply+5, sizeof(uint32_t));

		//cerr << "Got challenge " << challenge << endl;
		return this->challenge;
	} else {
		cerr << __func__ << "Error getting challenge " << strerror(errno) << endl;
		//throw invalid_argument("Unabe to resolve hostname");
	}
	return 0;
}

// http://developer.valvesoftware.com/wiki/Server_Queries
bool HL2ServerInfo::update(int timeout_sec, int timeout_usec)
{
	short *appId = NULL;
	char squery[] = {
		0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69,
		0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00
	};
	char buf[BUFSIZ];
	char values[64];
	char *tmp;
	int r;
	int sock = this->openConnection();
	string str;

	if((r = write(sock, squery, sizeof(squery))) <0) {
		cerr << __func__ << ": " << strerror(errno) << endl;
		this->closeConnection();
		return false;
	}

	if((r = this->readMultiReply(sock, buf, sizeof(buf), timeout_sec, timeout_usec))>0) {
		tmp = buf + 4;
		if(*tmp != 0x49) {
			cerr << __func__ << ": got wrong packet type " << (0xff & *tmp) << endl;
			this->closeConnection();
			return false;
		}
		tmp++;
		tmp++;
		attributes["name"] = tmp;
		tmp += strlen(tmp);
		tmp++;
		attributes["map"] = tmp;
		tmp += strlen(tmp);
		tmp++;
		attributes["gametype"] = tmp;
		tmp += strlen(tmp);
		tmp++;
		attributes["gamedescription"] = tmp;
		tmp += strlen(tmp);
		tmp++;

		appId = (short *) tmp;
		snprintf(values, sizeof(values), "%u", *appId);
		str = values;
		attributes["appId"] = str;
		tmp += sizeof(short);

		snprintf(values, sizeof(values), "%u", 0xff & *tmp);
		str = values;
		attributes["numplayers"] = str;
		tmp ++;
		snprintf(values, sizeof(values), "%u", 0xff & *tmp);
		attributes["maxplayers"] = values;
		tmp ++;
		snprintf(values, sizeof(values), "%u", 0xff & *tmp);
		attributes["bots"] = values;
		tmp ++;
		switch(*tmp) {
			case 'p':
				attributes["proxy"] = "1";
			case 'd':
				attributes["dedicated"] = "1";
				break;
			case 'l':
			default:
				attributes["dedicated"] = "0";
				break;
		}
		tmp ++;
		attributes["os"] = *tmp == 'w' ? "windows" : "linux";
		tmp ++;
		snprintf(values, sizeof(values), "%u", 0xff & *tmp);
		attributes["password"] = values;
		tmp ++;
		snprintf(values, sizeof(values), "%u", 0xff & *tmp);
		attributes["secure"] = values;
		tmp ++;
		attributes["version"] = tmp;

		return true;
	} else {
		cerr << __func__ << ": " << strerror(errno) << endl;
	}
	if(r<0)
		cerr << __func__ << ": select: " << strerror(errno) << endl;
	
	this->closeConnection();
	return false;
}

bool HL2ServerInfo::updateRules(int timeout_sec, int timeout_usec)
{
	uint32_t challenge;
	char buf[BUFSIZ * 3];
	int r;
	bool ret = false;

	challenge = this->getChallenge(timeout_sec, timeout_usec);
	if(!challenge)
		return false;
	char request[] = { 0xff, 0xff, 0xff, 0xff, 0x56, 0, 0, 0, 0 };
	memcpy(request+5, &challenge, sizeof(uint32_t));
	write(sock, request, sizeof(request));


	r = this->readMultiReply(sock, buf, sizeof(buf), timeout_sec, timeout_usec);

	//while((r = select(sock+1, &rset, NULL, NULL, &tv))>0) {
	if(r>6) {
		//bool hasMore = false;
		int i;
		short *numrules;
		ret = true;
		if(buf[4] != 'E') {
			cerr << "Got wrong package " << buf[5] << endl;
			//break;
		} else {
			numrules = (short *)(buf+5);
			for(i=7;i<r;) {
				char *rule = buf + i;
				char *value = buf + i + strlen(rule) + 1;
				i += strlen(rule) + 1 + strlen(value) + 1;
				rules[rule] = value;
				//cerr << "\t" << rule << "=" << value << endl;
			}
		}
		//if(!hasMore)
		//	break;
	} else {
		//break;
	}
	//	tv.tv_sec = timeout_sec;
	//	tv.tv_usec = timeout_usec;
	//}
	// as we are not reading all replies, we should close the connection to avoid 
	this->closeConnection();

	return ret;
}

HL2ServerInfo::HL2ServerInfo(HL2ServerInfo & arg)
{
	map<string, string> othermap = arg.getRules();
	map<string, string>::iterator begin;
	for(begin = othermap.begin(); begin != othermap.end();begin++) {
	}
	memcpy(&addr, arg.getAddress(), sizeof(struct sockaddr_in));
}

int HL2ServerInfo::openConnection()
{
	struct sockaddr_in laddr = {};
	if(sock>=0)
		return sock;
	
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock < 0) {
		cerr << __func__ << ": " << strerror(errno) << endl;
		return -1;
	}
	laddr.sin_family = AF_INET;
	bind(sock, (struct sockaddr *) &laddr, sizeof(struct sockaddr_in));
	connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));

	return sock;
}

int HL2ServerInfo::closeConnection()
{
	if(sock>=0) {
		int s = sock;
		sock = -1;
		return close(s);
	}
	return -1;
}

/*HL2ServerInfo & operator=(const ServerInfo &arg)
{
	memcpy(&this->addr, arg.getAddress(), sizeof(struct sockaddr_in));
	return *this;
}*/

HL2ServerInfo & HL2ServerInfo::operator=(const HL2ServerInfo &arg)
{
	memcpy(&addr, arg.getAddress(), sizeof(struct sockaddr_in));
	return *this;
}

const struct sockaddr_in *HL2ServerInfo::getAddress() const
{
	return &addr;
}

const std::map<std::string, std::string> & HL2ServerInfo::getRules()
{
	return this->rules;
}

const std::map<std::string, std::string> & HL2ServerInfo::getAttributes()
{
	return this->attributes;
}

int HL2ServerInfo::readMultiReply(int fd, char *buf, size_t buflen, int timeout_sec, int timeout_usec)
{
	bool done = false;
	int qtd=0, count=0, pos=0;
	struct timeval tv;
	int r;
	fd_set rset;
	char reply[1500];
	tv.tv_sec = timeout_sec;
	tv.tv_usec = timeout_usec;

	FD_ZERO(&rset);
	FD_SET(fd, &rset);
	while(!done && (r = select(fd+1, &rset, NULL, NULL, &tv))>0) {
		int32_t *v;
		if((r = read(sock, reply, sizeof(reply))) < 0) {
			cerr << __func__ << ": " << strerror(errno) << endl;
			break;
		}
		v = (int32_t *)reply;
		switch(*v) {
			case -1:
				memcpy(buf, reply, r);
				pos+=r;
				done = true;
				//cout << "Single pkt" <<endl;
				break;
			case -2:
				qtd = 0xf & reply[8];
				count = 0xf & (reply[8] >> 4);
				memcpy(buf+pos, reply + 9, r - 9);
				pos+= r - 9;
				if(count == qtd - 1)
					done = true;
				//cout << "Multi pkt: qtd: " << qtd << " cur: " << count <<endl;
				break;
		}
		
		FD_ZERO(&rset);
		FD_SET(sock, &rset);
	}
	return pos;
}
